
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Engine/XML/Serializer.h>
#include <Liberty3D/Engine/ISerializableFactory.h>

using namespace Liberty::Engine;
using namespace Liberty::Engine::Xml;

#include <algorithm>
#include <stdexcept>

#if defined(__LP64__)
#define LY_IN64 long
#else
#define LY_IN64 long long
#endif

#ifdef WIN32
#include <locale.h>

#define locale_t _locale_t
#else
#include <xlocale.h>
#endif

namespace Liberty { namespace Engine {
    locale_t getCLocale();
}}

static size_t string_to_size_t(const char* name) {
#if defined(WIN32)
    return _strtoul_l(name, nullptr, 16, getCLocale());
#elif defined(WIN64)
    return _strtoui64_l(name, nullptr, 16, getCLocale());
#else
    return strtoul_l(name, nullptr, 16, getCLocale());
#endif
}

Reader::Reader(std::shared_ptr<INode> node, ISerializableFactory* factory, bool factoryDelete) :
    _factory      (factory),
    _factoryDelete(factoryDelete),
    _status       (FirstCall)
{
    if (node->type() == INode::ELEMENT) {
        if (node->data() == "objects") {
            _objectsNode = node;
        }
    }

    if (!_objectsNode) {
        _objectsNode = node->firstChildElement("objects");
    }

    if (!_objectsNode) {
        throw std::runtime_error("Can't find \'objects\' node");
    }

    _stack.push_back(_objectsNode);
    _current = _objectsNode;
    _objects.resize(string_to_size_t(_objectsNode->attribute("count").c_str()) + 1);

    _statuses.push_back(Normal);
}

Reader::~Reader() {
    if (_factoryDelete) {
        delete _factory;
    }
}

bool Reader::isReading() const {
    return true;
}

template <typename T, typename Pred>
bool Reader::serializePrimitive(const char* name, T& v, T dv, bool mandatory, Pred pred) {
    if (!_current) {
        v = dv;
        return false;
    }

    std::string s;

    if (mandatory) {
        if (_current->hasAttribute(name)) {
            s = _current->attribute(name);
        }
        else {
            v = dv;
            return false;
        }
    }
    else {
        std::shared_ptr<INode> node = getNode(name);

        if (node) {
            s = getValue(node).c_str();
        }
        else {
            v = dv;
            return false;
        }
    }

    v = pred(s);
    return true;
}

bool Reader::serialize(const char* name, bool& v, bool dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> bool {
        return s == "true";
    });
}

bool Reader::serialize(const char* name, int& v, int dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> int {
#ifdef WIN32
        return _strtol_l(s.c_str(), nullptr, 10, getCLocale());
#else
        return (int)strtol_l(s.c_str(), nullptr, 10, getCLocale());
#endif
    });
}

bool Reader::serialize(const char* name, unsigned int& v, unsigned int dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> unsigned int {
#ifdef WIN32
        return _strtoul_l(s.c_str(), nullptr, 10, getCLocale());
#else
        return (unsigned int)strtoul_l(s.c_str(), nullptr, 10, getCLocale());
#endif
    });
}

bool Reader::serialize(const char* name, LY_IN64& v, LY_IN64 dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> LY_IN64 {
#ifdef WIN32
        return _strtoi64_l(s.c_str(), nullptr, 10, getCLocale());
#else
#ifdef __LP64__
        return strtol_l(s.c_str(), nullptr, 10, getCLocale());
#else
        return strtoll_l(s.c_str(), nullptr, 10, getCLocale());
#endif
#endif
    });
}

bool Reader::serialize(const char* name, unsigned LY_IN64& v, unsigned LY_IN64 dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> unsigned LY_IN64 {
#ifdef WIN32
        return _strtoui64_l(s.c_str(), nullptr, 10, getCLocale());
#else
#ifdef __LP64__
        return strtoul_l(s.c_str(), nullptr, 10, getCLocale());
#else
        return strtoull_l(s.c_str(), nullptr, 10, getCLocale());
#endif
#endif
    });
}

bool Reader::serialize(const char* name, float& v, float dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> float {
#ifdef WIN32
        return (float)_strtod_l(s.c_str(), nullptr, getCLocale());
#else
        return (float)strtod_l(s.c_str(), nullptr, getCLocale());
#endif
    });
}

bool Reader::serialize(const char* name, double& v, double dv, bool mandatory) {
    return serializePrimitive(name, v, dv, mandatory, [] (const std::string& s) -> double {
#ifdef WIN32
        return _strtod_l(s.c_str(), nullptr, getCLocale());
#else
        return strtod_l(s.c_str(), nullptr, getCLocale());
#endif
    });
}

bool Reader::serialize(const char* name, std::string& v, const std::string& dv, bool mandatory) {
    if (!_current) {
        v = dv;
        return false;
    }

    if (mandatory) {
        v = _current->attribute(name, dv);
    }
    else {
        std::shared_ptr<INode> node = getNode(name);

        if (node) {
            v = getValue(node);
        }
        else {
            v = dv;
            return false;
        }
    }

    return true;
}

std::shared_ptr<ISerializable> Reader::readObject(const char* name) {
    if (name == nullptr && _status == FirstCall) {
        readObjects();
        return _objects[1];
    }

    if (_current) {
        std::shared_ptr<INode> node = getNode(name);

        if (node) {
            std::string valStr = getValue(node);

            if (valStr.length()) {
                size_t refID = string_to_size_t(valStr.c_str());

                return _objects[refID];
            }
        }
    }

    return std::shared_ptr<ISerializable>();
}

void Reader::readObjects() {
    std::shared_ptr<INode> objectNode = _objectsNode->firstChildElement("object");

    size_t id = 1;

    while (objectNode) {
        _objects[id++] = _factory->create(objectNode->attribute("class").c_str());
        objectNode = objectNode->nextSiblingElement("object");
    }

    objectNode = _objectsNode->firstChildElement("object");

    id = 1;

    while (objectNode) {
        _current = objectNode;
        _stack.push_back(_current);

        _objects[id++]->serializeObject(this);

        _current = _stack[_stack.size() - 1];
        _stack.pop_back();

        objectNode = objectNode->nextSiblingElement("object");
    }

    {
        std::vector<std::shared_ptr<ISerializable>>::const_iterator it(_objects.begin()), end(_objects.end());

        ++it;
        for (; it != end; ++it) {
            (*it)->serializeEnd(this);
        }
    }
}

void Reader::writeObject(const char* name, const std::shared_ptr<ISerializable>&) {
    throw std::exception();
}

size_t Reader::beginCollection(const char* name) {
    std::shared_ptr<INode> node = getNode(name);

    _current = node;
    _stack.push_back(node);

    _status = Collection;
    _statuses.push_back(_status);

    _current = _current->firstChildElement("entry");
    _stack.push_back(_current);
    return string_to_size_t(node->attribute("count").c_str());
}

void Reader::endCollection(const char* name) {
    _stack.pop_back();
    _stack.pop_back();
    _current = _stack[_stack.size() - 1];

    _statuses.pop_back();
    _status = _statuses[_statuses.size() - 1];
}

size_t Reader::beginDictionary(const char* name) {
    std::shared_ptr<INode> node = getNode(name);

    _current = node;
    _stack.push_back(node);

    _status  = Dictionary;
    _statuses.push_back(_status);

    _current = _current->firstChildElement("key");
    _stack.push_back(_current);
    return string_to_size_t(node->attribute("count").c_str());
}

void Reader::endDictionary(const char*) {
    _stack.pop_back();
    _stack.pop_back();
    _current = _stack[_stack.size() - 1];

    _statuses.pop_back();
    _status = _statuses[_statuses.size() - 1];
}

void Reader::beginStruct(const char* name, const char*) {
    std::shared_ptr<INode> node = getNode(name);

    _current = node;
    _stack.push_back(node);
}

void Reader::endStruct(const char*, const char*) {
    _stack.pop_back();
    _current = _stack[_stack.size() - 1];
}

std::shared_ptr<INode> Reader::getNode(const char* name) {
    if (name == nullptr) {
       std::shared_ptr<INode> n(_current);

        switch (_status) {
        case Dictionary:
            _status  = DictionaryValue;
            _current = _current->nextSiblingElement("value");
            _stack[_stack.size() - 1] = _current;
            return n;
        case DictionaryValue:
            _status  = Dictionary;
            _current = _current->nextSiblingElement("key");
            _stack[_stack.size() - 1] = _current;
            return n;
        case Collection:
            _current = _current->nextSiblingElement("entry");
            _stack[_stack.size() - 1] = _current;
            return n;
        case FirstCall:
        case Normal:
            throw std::exception();
        }
    }

    return _current->firstChildElement(name);
}

std::string Reader::getValue(const std::shared_ptr<INode>& node) {
    std::string v;
    std::vector<std::shared_ptr<INode>> childs;

    node->childs(childs);

    std::vector<std::shared_ptr<INode>>::const_iterator it, end(childs.end());

    for (it = childs.begin(); it != end; ++it) {
        const std::shared_ptr<INode>& child(*it);

        switch (child->type()) {
        case INode::TEXT:
        case INode::CDATA:
            v += child->data();
            break;
        default:
            break;
        }
    }

    return v;
}

void Reader::close() {
}